﻿/*
 *  DFUtil - a utility for creating and implementing blueprints in Dwarf Fortress.
 *  
 *  Copyright (C) 2009  Katharine Gillis
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/.
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace DFUtil
{
    /// <summary>
    /// A custom grid class to display the DFUtil blueprints.
    /// </summary>
    public partial class Grid : UserControl
    {
        #region Variables

        private Tileset _tileset;
        private Rectangle _viewPort;
        private Bitmap _grid;
        private GridState _state;

        #endregion

        #region Properties
        private GridMode _mode;
        public GridMode Mode
        {
            get { return _mode; }
            set
            {
                _mode = value;
                _state = GridState.Create(this, _viewPort, new Rectangle(new Point(0, 0), _grid.Size));
            }
        }
        #endregion

        #region Constructor

        /// <summary>
        /// Creates the Grid object.
        /// </summary>
        public Grid()
        {
            InitializeComponent();
            _grid = new Bitmap(1, 1);
            _tileset = new Tileset();
        }

        #endregion

        #region User control callback functions

        private void Grid_Load(object sender, EventArgs e)
        {
            _viewPort.Size = new Size(Width, Height);
            _grid = new Bitmap((Width / _tileset.GridWidth + 100) * _tileset.GridWidth + 2, (Height / _tileset.GridHeight + 10) * _tileset.GridHeight + 1);
            _viewPort.Location = new Point(0, 0);
            Mode = GridMode.Pan;
            InvalidateGrid();
        }

        private void Grid_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.DrawImage(_grid, new Rectangle(new Point(0, 0), _viewPort.Size), _viewPort, GraphicsUnit.Pixel);
        }

        private void Grid_Resize(object sender, EventArgs e)
        {
            _viewPort.Size = new Size(Width, Height);
            if (_grid.Width < Width || _grid.Height < Height)
            {
                _grid = new Bitmap((Width / _tileset.GridWidth + 100) * _tileset.GridWidth + 1, (Height / _tileset.GridHeight + 10) * _tileset.GridHeight + 1);
                InvalidateGrid();
            }
        }

        private void Grid_MouseMove(object sender, MouseEventArgs e)
        {
            GridStateMouseResult result = _state.Move(e);
            if (result.NeedsInvalidate)
            {
                _viewPort.Location = result.NewViewPortLocation;
                Invalidate();
            }
        }

        private void Grid_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                _state.LeftClick(e);
            }
            else if (e.Button == MouseButtons.Right)
            {
                _state.RightClick(e);
            }
        }

        #endregion

        #region Private members

        private Rectangle Intersection(Rectangle first, Rectangle second)
        {
            Rectangle result = Rectangle.Intersect(first, second);
            return (result.Width == 0 && result.Height == 0 ? Rectangle.Empty : result);
        }

        private Bitmap DrawGrid()
        {
            return DrawGrid(new Rectangle(new Point(0, 0), _grid.Size));
        }

        private Bitmap DrawGrid(Rectangle rect)
        {
            Bitmap gridPiece = new Bitmap(rect.Width, rect.Height);
            Graphics g = Graphics.FromImage(gridPiece);
            g.FillRectangle(new SolidBrush(Color.Black), new Rectangle(new Point(0, 0), gridPiece.Size));

            Pen gridLine = new Pen(Color.FromArgb(25, 25, 25));

            int x = 0 - (rect.X % _tileset.GridWidth);
            int y = 0 - (rect.Y % _tileset.GridHeight);
            while (x <= gridPiece.Width || y <= gridPiece.Height)
            {
                if (x <= _grid.Width)
                {
                    g.DrawLine(gridLine, new Point(x, 0), new Point(x, _grid.Height));
                }

                if (y <= _grid.Height)
                {
                    g.DrawLine(gridLine, new Point(0, y), new Point(_grid.Width, y));
                }
                x += _tileset.GridWidth;
                y += _tileset.GridHeight;
            }

            g.FillRectangle(new SolidBrush(Color.Aqua), new Rectangle(new Point(100, 100), new Size(100, 100)));
            return gridPiece;
        }

        private void InvalidateGrid()
        {
            Graphics g = Graphics.FromImage(_grid);
            g.DrawImage(DrawGrid(), new Point(0, 0));
            Invalidate();
        }

        public void InvalidateGrid(Rectangle rect)
        {
            Graphics g = Graphics.FromImage(_grid);
            g.DrawImage(DrawGrid(rect), rect.Location);
            Invalidate(Intersection(rect, _viewPort));
        }
        #endregion
    }

    public enum GridMode { Pan };

    public abstract class GridState
    {
        #region Variables
        protected Grid _parent;
        #endregion

        #region Constructor
        public GridState(Grid parent)
        {
            _parent = parent;
        }
        #endregion

        #region Factory method
        public static GridState Create(Grid parent, Rectangle viewPort, Rectangle grid)
        {
            switch (parent.Mode)
            {
                case GridMode.Pan:
                    return new PanState(parent, viewPort, grid);
                default:
                    throw new ArgumentException("Bad Grid mode");
            }
        }
        #endregion

        #region Abstract methods
        public virtual GridStateMouseResult LeftClick(MouseEventArgs e) { return new GridStateMouseResult(false, Point.Empty); }
        public virtual GridStateMouseResult Move(MouseEventArgs e) { return new GridStateMouseResult(false, Point.Empty); }
        public virtual GridStateMouseResult RightClick(MouseEventArgs e) { return new GridStateMouseResult(false, Point.Empty); }
        #endregion
    }

    public class PanState : GridState
    {
        #region Variables
        private Rectangle _viewPort;
        private Rectangle _grid;
        private Point _lastMouseLocation;
        #endregion

        #region Constructor
        public PanState(Grid parent, Rectangle viewPort, Rectangle grid)
            : base(parent)
        {
            _viewPort = viewPort;
            _grid = grid;
            _lastMouseLocation = new Point(-1, -1);
        }
        #endregion

        #region GridState methods
        public override GridStateMouseResult Move(MouseEventArgs e)
        {
            GridStateMouseResult result = new GridStateMouseResult(false, Point.Empty);
            if (e.Button == MouseButtons.Left)
            {
                Point diff = new Point(_lastMouseLocation.X - e.X, _lastMouseLocation.Y - e.Y);
                //Point diff = new Point(31, 31);
                if (!diff.IsEmpty)
                {
                    int newX = _viewPort.X + diff.X;
                    if (newX < 0)
                        newX = 0;
                    else if (newX > _grid.Width - _viewPort.Width)
                        newX = _grid.Width - _viewPort.Width;

                    int newY = _viewPort.Y + diff.Y;
                    if (newY < 0)
                        newY = 0;
                    else if (newY > _grid.Height - _viewPort.Height)
                        newY = _grid.Height - _viewPort.Height;

                    Point newViewPortPoint = new Point(newX, newY);
                    if (newViewPortPoint.X != _viewPort.X || newViewPortPoint.Y != _viewPort.Y)
                    {
                        _viewPort.Location = newViewPortPoint;
                        result = new GridStateMouseResult(true, newViewPortPoint);
                    }
                }
            }

            _lastMouseLocation = e.Location;

            return result;
        }

        public override GridStateMouseResult LeftClick(MouseEventArgs e)
        {
            _lastMouseLocation = e.Location;
            return base.LeftClick(e);
        }
        #endregion
    }

    public class GridStateMouseResult
    {
        #region Properties
        private bool _needsInvalidate;
        public bool NeedsInvalidate
        {
            get { return _needsInvalidate; }
        }

        private Point _newViewPortLocation;
        public Point NewViewPortLocation
        {
            get { return _newViewPortLocation; }
        }
        #endregion

        #region Constructor
        public GridStateMouseResult(bool needsInvalidate, Point newViewPortLocation)
        {
            _needsInvalidate = needsInvalidate;
            _newViewPortLocation = newViewPortLocation;
        }
        #endregion
    }
}
